/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package coalescent.cef.command;

import cef.command.Command;
import cef.command.CommandException;

import coalescent.cef.event.VisualizeGeneTreeEvent;
import coalescent.phylogeny.GeneTree;
import coalescent.phylogeny.GeneTree.MutationEdgeData;

import commons.util.graph.Edge;
import commons.util.graph.LeafPath;
import commons.util.graph.Node;

import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.layout.TreeLayout;
import edu.uci.ics.jung.graph.DelegateTree;
import edu.uci.ics.jung.graph.Forest;
import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.decorators.ConstantDirectionalEdgeValueTransformer;
import edu.uci.ics.jung.visualization.decorators.EdgeShape;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position;
import edu.uci.ics.jung.visualization.util.VertexShapeFactory;

import org.apache.commons.collections15.Transformer;

import java.awt.*;
import java.util.List;

import javax.swing.*;

/**
 * Visualizes infinite-sites data as a gene tree.
 *
 * @author Susanta Tewari
 * @version 1.0 Dec 21, 2010
 */
public class VisualizeGeneTreeCommand extends Command<VisualizeGeneTreeEvent> {

    /** Field description */
    private VisualizationViewer<Node<String, MutationEdgeData>, Edge<String, MutationEdgeData>> figure;

    /** Field description */
    private JPanel figurePanel;

    /** Field description */
    private Forest<Node<String, MutationEdgeData>, Edge<String, MutationEdgeData>> jungForest;

    /** Field description */
    private GeneTree geneTree;

    /**
     * Constructs ...
     *
     *
     * @param event
     */
    public VisualizeGeneTreeCommand(VisualizeGeneTreeEvent event) {

        super(event);

        this.geneTree = event.getGeneTree();
    }

    @Override
    public void executeImpl() throws CommandException {


        // 4. do the job. build JUNG forest for visualization
        jungForest = new DelegateTree<>();

        jungForest.addVertex(geneTree.getRootNode());
        createJungForest(jungForest, geneTree.getRootNode());
        setCmdFinished(true);
        fireOutputEvent(getCommandOutputEvent());
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public Forest<Node<String, MutationEdgeData>, Edge<String, MutationEdgeData>> getForest() {

        checkCmdFinished();

        return jungForest;
    }

    /**
     * Returns gene tree as an instance of {@link Forest} suitable to be visualized via the JUNG
     * framework.
     *
     * @return the figure panel
     */
    public JPanel getGeneTreeFigurePanel() {

        if (figurePanel == null) {

            figurePanel = createFigurePanel((VisualizationViewer<Node<String, MutationEdgeData>,
                    Edge<String, MutationEdgeData>>) getGeneTreeFigure());
        }

        return figurePanel;
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public JPanel getGeneTreeFigure() {

        if (figure == null) {
            figure = createGeneTreeFigure(jungForest, geneTree);
        }

        return figure;
    }

    /**
     * Method description
     *
     *
     * @param tree
     * @param node
     */
    private void createJungForest(Forest<Node<String, MutationEdgeData>,
            Edge<String, MutationEdgeData>> tree, Node<String, MutationEdgeData> node) {

        List<Node<String, MutationEdgeData>> children = node.getChildren();

        for (Node<String, MutationEdgeData> child : children) {

            tree.addEdge(node.getChildEdge(child), node, child);

            if (!child.isLeaf()) {
                createJungForest(tree, child);
            }
        }
    }

    /**
     * Method description
     *
     *
     * @param tree
     * @param geneTree
     *
     * @return
     */
    private VisualizationViewer<Node<String, MutationEdgeData>,
                                Edge<String,
                                     MutationEdgeData>> createGeneTreeFigure(Forest<Node<String,
                                         MutationEdgeData>, Edge<String,
                                             MutationEdgeData>> tree, final GeneTree geneTree) {

        Layout<Node<String, MutationEdgeData>, Edge<String, MutationEdgeData>> layout =
            new TreeLayout<>(tree, 100, 100);
        final VisualizationViewer<Node<String, MutationEdgeData>, Edge<String, MutationEdgeData>> vv =
            new VisualizationViewer<>(layout);


        // adjust vertex label
        vv.getRenderContext().setVertexLabelTransformer(new Transformer<Node<String,
                MutationEdgeData>, String>() {

            @Override
            public String transform(Node<String, MutationEdgeData> node) {

                String val = node.getData();

                if (node.isLeaf()) {


                    // find frequency for leaf node
                    // find its path. a leaf node on a gene tree has only one path
                    for (LeafPath<String, MutationEdgeData> path : geneTree.getAlleles()) {

                        if (path.getLeaf().equals(node)) {

                            val += ":" + geneTree.getAlleleFrequency(path);

                            return val;
                        }
                    }
                }

                return val;
            }

        });


        // adjust vertex color
        vv.getRenderContext().setVertexFillPaintTransformer(new Transformer<Node<String,
                MutationEdgeData>, Paint>() {

            @Override
            public Paint transform(Node<String, MutationEdgeData> node) {
                return Color.WHITE;
            }

        });


        // adjust vertex size
        final VertexShapeFactory<Node<String, MutationEdgeData>> vertexShapeFactory =
            new VertexShapeFactory<>(new Transformer<Node<String, MutationEdgeData>, Integer>() {

            @Override
            public Integer transform(Node<String, MutationEdgeData> node) {

                int offset = 15;
                String val = vv.getRenderContext().getVertexLabelTransformer().transform(node);
                int result = vv.getFontMetrics(vv.getFont()).stringWidth(val) + offset;

                return Math.min(result, 75);
            }

        }, new Transformer<Node<String, MutationEdgeData>, Float>() {

            @Override
            public Float transform(Node node) {
                return 1.0f;
            }

        });

        vv.getRenderContext().setVertexShapeTransformer(new Transformer<Node<String,
                MutationEdgeData>, Shape>() {

            @Override
            public Shape transform(Node<String, MutationEdgeData> node) {
                return vertexShapeFactory.getEllipse(node);
            }

        });


        // vertex label position
        vv.getRenderer().getVertexLabelRenderer().setPosition(Position.CNTR);


        // configure the edge
        vv.getRenderContext().setEdgeLabelTransformer(new ToStringLabeller<Edge<String,
                MutationEdgeData>>());
        vv.getRenderContext().setEdgeShapeTransformer(new EdgeShape.Line<Node<String,
                MutationEdgeData>, Edge<String, MutationEdgeData>>());
        vv.getRenderContext().setEdgeLabelClosenessTransformer(
            new ConstantDirectionalEdgeValueTransformer<Node<String, MutationEdgeData>,
                Edge<String, MutationEdgeData>>(0.5, 0.5));

        return vv;
    }

    /**
     * Method description
     *
     *
     * @param vv
     *
     * @return
     */
    private JPanel createFigurePanel(final VisualizationViewer<Node<String, MutationEdgeData>,
            Edge<String, MutationEdgeData>> vv) {


        // mouse
        final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse();

        vv.setGraphMouse(graphMouse);


        // add a combobox to change mouse mode
        JComboBox modeBox = graphMouse.getModeComboBox();

        modeBox.addItemListener(graphMouse.getModeListener());
        graphMouse.setMode(ModalGraphMouse.Mode.PICKING);


        // collect all the controls
        JToolBar controls = new JToolBar();

        controls.add(modeBox);


        // zoom control for the graph
        final GraphZoomScrollPane zoomPanel = new GraphZoomScrollPane(vv);


        // add the controls
        JPanel geneTreeVisualComp = new JPanel(new BorderLayout());

        geneTreeVisualComp.add(controls, BorderLayout.PAGE_START);
        geneTreeVisualComp.add(zoomPanel, BorderLayout.CENTER);

        return geneTreeVisualComp;
    }
}
